上一章谈到感知机。理论上感知机能表示任意的复杂函数,但是它有很明显的缺点:权重必须手动设定。
而本章介绍的神经网络,它的一个重要性质就是可以自动地从数据中学习到合适的权重参数。

一、从感知机到神经网络

1)神经网络的例子

神经网络分为三层:分别为输入层、中间层、输出层。由于中间层是不可见的,也被称为“隐藏层”。本书把这三层依次称为第0层、第一层、第二层。
在连接方式上,神经网络与感知机没有区别。

2)再论感知机

感知机的函数可以表达为

其中x、y为输入,b为偏置。这样的感知机在实现时便需要对x、 y、 b分开处理。

其实我们可以将x、y、b 统一处理。假设b也是一个输出、并且它的值恒为b,权重恒为1。
另外,我们可以引入函数h(x),, 这样,上面的函数可以表示为$h(ux + vy + b)$.

3)激活函数登场

上面提到的$h(x)$,它可以将输入信号的总和处理成输出信号,这个函数便称为激活函数
激活函数的计算过程也就可以拆分为:

  • 计算 c = ux + vy + b. c 计算所有输入信号的加权和
  • 计算 $h(c)$

二、激活函数

前面介绍的激活函数,以某个阈值为界,当输入超过阈值后就改变输出,这种函数称为阶跃函数,激活函数还有其他的类型

1)sigmoid函数

神经网络中经常用到sigmiod函数

e即是自然对数的底数。感知机和神经元的主要区别就在于激活函数

2)阶跃函数的实现

前面的阶跃函数只能处理数组。下面我们写一个能处理矩阵的阶跃函数:

import numpy as np
def step_function(x):
    y = x > 0
    return y.astype(np.int)
x = np.array([-1.0, 1.0, 2.0])
print(step_function(x))

输出为

[0 1 1]

astyped()的方法可以将矩阵元素转换为期望的类型。上面是将bool型转换为np.int型

3)阶跃函数的图像

运用matplotlib

import numpy as np
import matplotlib.pylab as plt
def step_function(x):
    return np.array(x > 0, dtype=np.int)
x = np.array([-1.0, 1.0, 2.0])

x = np.arange(-5.0,5.0,0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y轴范围
plt.show()

得到的图像如图:

它的函数值呈阶梯形变化,故称阶跃函数

4) sigmoid函数的实现

import numpy as np
import matplotlib.pylab as plt
def sigmoid(x):
    return 1 / (1 + np.exp(-x)) 
x = np.arange(-5.0,5.0,0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y轴范围
plt.show()

函数图像如图:

5)sigmoid函数和阶跃函数的比较

  • 不同点:阶跃函数是“间断”的,sigmoid函数是连续变化的。
  • 相同点:两者的函数值均在0和1之间

6)线性函数与非线性函数

阶跃函数和sigmoid函数还有一个共同点:两者均是非线性函数。神经网络的激活函数必须是非线性函数。否则,加深神经网络的层数就没有意义了。线性函数无论如何叠加,其效果均为线性

7) ReLU函数

ReLU函数最近被广泛用作激活函数,其表达式为:

其代码实现:

def relu(x):
    return np.maximum(0,x)

三、多维数组的运算

1)多维数组

import numpy as np
A = np.array([[1,2],[1,4],[8,9]])
print(A)
print(np.ndim(A)) # A的维数
print(A.shape) # A的形状

输出结果:

[[1 2]
 [1 4]
 [8 9]]
2
(3, 2)

2)矩阵的点积(内积)运算:

import numpy as np
A = np.array([[1,2],[1,4],[8,9]])
B = np.array([[1,2,3],[1,3,4]])
print(np.dot(A,B))

输出为:

[[ 3  8 11]
 [ 5 14 19]
 [17 43 60]]

而矩阵运算在神经网络中有什么意义?
以上面这段代码为例:A 相当于输入信号, B相当于权重

A 有三组输入数据,每组分别有两个量(即为x, y)
B 有三个神经元,第一个神经元的权重分别为1、1第二个神经元的权重分别为 2、3。
而最后得到的是三个神经元的三次数据。

矩阵运算可以简洁地表示多个神经元、多组输入数据的结果

四、3层神经网络的实现

我们要实现下图中的三层神经网络:

1) 符号确认

我们先导入 等符号。这些符号仅在本节中使用右上角的(1)表示这是第1层的神经元、第一层的权重 表示从第2层(即第3层的前一层)第1个神经元到第(3)层第2个神经元的权重.表示第1层第2个神经元

2)各层间信号传递的实现

输入层到第一层


其中注意偏置神经元“1”,而表示偏置的b右下角的索引号只有一个数字。此时

表示成矩阵运算则为:

其中:,,,

下面我们用Numpy数组来实现

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x)) 

X = np.array([1.0, 0.5])
W1 = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
B1 = np.array([0.1, 0.2, 0.3])
A1 = np.dot(X, W1) + B1
print(A1)
print(sigmoid(A1))

输出:

  • [0.3 0.7 1.1]
  • [0.57444252 0.66818777 0.75026011]

上面的代码中用到了先前的激活函数,实际上代码的过程如下图:

第1层到第2层

# 继续使用前面的代码
W2 = np.array([[0.1, 0.4],[0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1,0.2])
A2 = np.dot(Z1,W2) + B2
Z2 = sigmoid(A2)
print(Z2)

输出:

  • [0.3 0.7 1.1]

第2层到输出层

def identity_function(x):
    return x
W3 = np.array([[0.1, 0.3],[0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3)

这里用到了船新的激活函数:identity_function,它其实啥也不干,单纯为了和前面格式统一
另外,输出层的激活函数用 表示,而不是

3)代码小结

import numpy as np

def identity_function(x):
    return x
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
def init_network():
    network = {}
    network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    #W1是第一层权重,b1是第一层偏置,可以看出第一层共有三个神经元,每个神经元接受两个输入信号
    network['W2'] = np.array([[0.1, 0.4],[0.2, 0.5], [0.3, 0.6]])
    network['b2'] = np.array([0.1, 0.2])
    network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
    network['b3'] = np.array([0.1, 0.2])
    return network
def forward(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x,W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1,W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2,W3) + b3
    y = identity_function(a3)

    return y

network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y)
  • 输出结果为:[0.31682708 0.69627909]

这里出现了“forward”函数,它表示从输入到输出方向的传递处理。之后会介绍后向(backward,从输出到输入方向的处理)

五、输出层的设计

神经网络可以用在分类和回归问题上。一般而言,回归问题用恒等函数,分类问题用softmax函数。

补充:机器学习大致分为分类问题和回归问题
分类问题举例:给一张图像,区分图像中的人是男性还是女性
回归问题举例:给一张图像,估测图像中人的体重

1)恒等函数和softmax函数

恒等函数前面已经接触到了,就是上面的identity_function函数。

softmax函数可以如下式这样表示

e是自然对数的底数。假设有n个神经元,计算第k个神经元的输出.
注意式子里面的a均为输入信号。softmax函数与之前的激活函数最大的不同在于它的输出受到所有输入信号的影响。

2)softmax函数的实现

下面用python对softmax函数进行实现:

import numpy as np

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

不过,当使用指数运算时会面临数据溢出的问题。上面的的函数,尽管最后的返回值y数字不会太大,但运算过程中的中间量sum_exp_a可能会非常非常大,比如输入中某个值为1000……这样很容易不能正确得到结果,所有需要改进
可以证明:

所以,输入矩阵中所有元素加/减某一常数,不影响最后的结果

改进如下:

import numpy as np

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

3)softmax函数的特征

import numpy as np

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

a = np.array([0.3, 2.9, 4.0])
y = softmax(a)
print(y)#输出:[0.01821127 0.24519181 0.73659691]
print(np.sum(y))#输出: 1.0

我直观的感觉:softmax是统计概率的函数,只不过这里面的概率利用了指数运算。
依照上面函数的输出结果,可以认为y[2]的概率最大(达到了0.73)

六、手写数字识别

假设学习工作已经全部完成,我们通过得到的参数实现神经网络的“推理处理”,推理处理也被称为神经网络的“前向传播”(forward propagation)。

1)MINIST数据集

关于MINIST数据集的介绍,请移步百度

加载:

这里需要用到书中的源码。仅需要理解加载的结果是:60000张训练图,10000张测试图

import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
(x_train, t_train),(x_test, t_test) = load_mnist(flatten=True,normalize=False,one_hot_label=False)
print(x_train.shape) #(60000, 784) 60000张28×28像素的训练图
print(t_train.shape) #(60000,)训练图的标签
print(x_test.shape) #(10000, 784)10000张测试图
print(t_test.shape) #(10000,)测试图的标签

load_minist的参数:

  • flatten:是否将图像表示为一维数组。若flatten=False,则图像是1×28×28的三维数组,flatten=True,图像是784个元素的一维数组
  • normalize:是否将图像正规化。若normalize=False,则数据的值为0~225,若normalize=True,则数据的值为0~1.0
  • one_hot_label: 当one_hot_label=True时,会将正确解的标签设为1,错误解设为0。

图像显示

import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist

from PIL import Image


def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

img = x_train[0]
label = t_train[0]
print(label)  # 5

print(img.shape)  # (784,)
img = img.reshape(28, 28)  # 把图像恢复为原来的尺寸
print(img.shape)  # (28, 28)

img_show(img)

这里用到PIL库。它的作用是将矩阵显示为图像
可以看到,x_train[0]保存的是图像“5”,t_train[0]保存的是整数5

2) 神经网络的推理处理

任务分析:我们要将MINIST数据集中的照片进行分类工作。
神经网络的输入层有784个单元(28×28像素的图片),输出层有10个单元(数字共有0到9,10个类别)。

而神经网络的很多参数是通过训练得到的。这里直接使用训练完的参数:

import sys, os
sys.path.append(os.pardir)
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax
def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, t_test


def init_network():
    with open("sample_weight.pkl", 'rb') as f:
        network = pickle.load(f)
    return network


def predict(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = softmax(a3)

    return y


x, t = get_data()
network = init_network() # 建立神经网络
accuracy_cnt = 0
for i in range(len(x)):
    y = predict(network, x[i]) #进行预测
    p= np.argmax(y) # 获得概率最高的元素的索引,完成预测
    if p == t[i]:
        accuracy_cnt += 1 #统计有多少张图片分类正确

print("Accuracy:" + str(float(accuracy_cnt) / len(x))) # 0.9352 即该神经网络的精度是93.52%

3)批处理

前面的代码进行了10000张784像素图片的分类工作。每张图片是逐个处理的。实际上完全可以把输入数据处理成10000×784的矩阵,这样就可以一次处理得到全部结果。
这种打包式的输入数据称为(batch)
批处理可以节约计算机的运算成本
批处理的代码:

#x, t = get_data()
#network = init_network() # 建立神经网络
#accuracy_cnt = 0
#for i in range(len(x)):
#    y = predict(network, x[i]) #进行预测
#    p= np.argmax(y) # 获得概率最高的元素的索引,完成预测
#    if p == t[i]:
#        accuracy_cnt += 1 #统计有多少张图片分类正确
x, t = get_data()
network = init_network() # 建立神经网络
accuracy_cnt = 0
batch_size = 100 #设定批的大小

for i in range(0,len(x),batcg_size):
    x_batch = x[i : i + batch_size]
    y_batch = predict(network, x_batch)
    p = np.argmax(y_batch, axis=1)
    accuracy_cnt += np.sum(p == t[i:i+batch_size])

书中源码把批的大小设定为100。